﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Windows7.Multitouch;
using Windows7.Multitouch.Manipulation;
using Windows7.Multitouch.WPF;

namespace Comic.Viewer
{
    /// <summary>
    /// Interaction logic for ctlComicViewer.xaml - the Control to Display a Comic-Page in Application
    /// </summary>
    public partial class ctlComicViewer : UserControl
    {
        /// <summary>object of a .Net Wrapper class for processing multitouch manipulation</summary>
        private ManipulationProcessor _ManipulationProcessor = new ManipulationProcessor(ProcessorManipulations.ALL);
        /// <summary>remider if Device has Multi-Touch Input</summary>
        private static bool IsMultitouchEnabled = TouchHandler.DigitizerCapabilities.IsMultiTouchReady;

        /// <summary>reference to the parent Window - for Multi-Touch Input</summary>
        private Window _ParentWindow;

        private bool _MouseDragActive;
        private Point _MouseDragStart;
        private Point _MouseDragLast;

        /// <summary>general reninder if Multi-touch is active</summary>
        private bool _MultiTouchActive;
        /// <summary>reminder if Multi-Touch Zoom is active</summary>
        private bool _MultiTouchZoom;
        /// <summary>reminder if Multi-Touch Rotate is active</summary>
        private bool _MultiTouchRotate;
        /// <summary>reminder if MT-Factory was already initialized</summary>
        private bool _Factoryinitialized;
        /// <summary>internal Setting for Height of Banner-Area</summary>
        private int _BannerAreaHeight;
        /// <summary>internal reminder for Zoom- or Scroll-Mode on WheelScroll-Event</summary>
        private bool _UseWheelForZoom;
        /// <summary>internal reminder for navigation visibility</summary>
        private bool _ShowNavigationControls;



        private int _MinImageSize;

        private int _ScrollStepSize;




        public int ScrollStepSize
        {
            get { return _ScrollStepSize; }
            set { _ScrollStepSize = value; }
        }
        

        public int MinImageSize
        {
            get { return _MinImageSize; }
            set { _MinImageSize = value; }
        }
        
        

        #region Properties

        /// <summary>Is Multi-Touch active for the Control? Get or Set...</summary>
        public bool MultiTouchActive
        {
            get { return this._MultiTouchActive; }
            set
            {
                if (this._MultiTouchActive != value) // if value has changed
                {
                    this._MultiTouchActive = value;
                    this._ApplyMultiTouchState_(); // avoid not required update
                }
            }
        }

        /// <summary>Is Multi-Touch Zoom active for the Control? Get or Set...</summary>
        public bool MultiTouchZoom
        {
            get { return this._MultiTouchZoom; }
            set { this._MultiTouchZoom = value; }
        }

        /// <summary>Is Multi-Touch Rotate active for the Control? Get or Set...</summary>
        public bool MultiTouchRotate
        {
            get { return this._MultiTouchRotate; }
            set
            {
                this._MultiTouchRotate = value;
                if (!value) // if rotate is disabled, reset angle
                {
                    this.trRotate.Angle = 0; // reset rotation
                }
            }
        }

        /// <summary>If TRUE, WheelScroll on Image-Control is used as zoom-input, if FALSE for setting the vertical scrolling</summary>
        public bool UseWheelForZoom
        {
            get { return this._UseWheelForZoom; }
            set { this._UseWheelForZoom = value; }
        }

        /// <summary>Show or hide the Navigation-Controls</summary>
        public bool ShowNavigationControls
        {
            get { return this._ShowNavigationControls; }
            set
            {
                if (this._ShowNavigationControls != value) // if changed, then apply
                {
                    this._ShowNavigationControls = value;
                    this._ApplyControlVisibility_();
                }
            }
        }

        /// <summary>Text on Button for Navigation to previous Page</summary>
        public string TextOnPreviousButton
        {
            get
            {
                return this.btnPreviousPage.Content.ToString();
            }
            set
            {
                this.btnPreviousPage.Content = value;
            }
        }

        /// <summary>Text on Button for Navigation to next Page</summary>
        public string TextOnNextButton
        {
            get
            {
                return this.btnNextPage.Content.ToString();
            }
            set
            {
                this.btnNextPage.Content = value;
            }
        }

        #endregion

        #region Constructor

        /// <summary>Constructor</summary>
        public ctlComicViewer()
        {
            InitializeComponent();
            this._BannerAreaHeight = 50;
            this._Factoryinitialized = false;
            this._MinImageSize = 25;
            this._ScrollStepSize = 20;
            this._MouseDragActive = false;
            // Event Register Single-Touch
            this._SingleTouchEvents_(true);
            // set navigation-controls to hidden on init
            this._ShowNavigationControls = false;
            this._ApplyControlVisibility_();
        }

        #endregion

        #region Functions

        /// <summary>Pass the Reference to the GUI-Window for Multi-Touch Event-Register</summary>
        /// <param name="ParentWindow">reference to the MainWinodw</param>
        public void SetParentWindow(ref Window ParentWindow)
        {
            this._ParentWindow = ParentWindow;
        }

        // ToDo: create
        // UpdateBannerHtml

        /// <summary>Load a Comic-Page to the Viewer and display it</summary>
        /// <param name="PageData">the Page to Display</param>
        /// <param name="HasNext">TRUE if Next-Button should be enabled</param>
        /// <param name="HasPrevious">TRUE if Previous-Button should be enabled</param>
        public void LoadPage(Comic.Common.cComicPage PageData, bool HasPrevious, bool HasNext)
        {
            this._LoadPageData_(PageData, HasPrevious, HasNext);
        }

        /// <summary>Trigger a Zoom-In from the GUI</summary>
        public void ZoomIn()
        {
            this._ZoomIn_();
        }

        /// <summary>Trigger a Zoom-Out from the GUI</summary>
        public void ZoomOut()
        {
            this._ZoomOut_();
        }

        /// <summary>Trigger a Zoom-Reset from the GUI</summary>
        public void ZoomReset()
        {
            this._ZoomReset_();
        }

        #endregion

        #region Multi-Touch Event-Handler

        // Stylus-Events

        /// <summary>The Handler for the Stylus-Move Event</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event-Args</param>
        void ctlComicViewer_StylusMove(object sender, StylusEventArgs e)
        {
            this._ManipulationProcessor.ProcessMove((uint)e.StylusDevice.Id, e.GetPosition(this).ToDrawingPointF());
        }

        /// <summary>The Handler for the Stylus-Up Event</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event-Args</param>
        void ctlComicViewer_StylusUp(object sender, StylusEventArgs e)
        {
            this._ManipulationProcessor.ProcessUp((uint)e.StylusDevice.Id, e.GetPosition(this).ToDrawingPointF());
        }

        /// <summary>The Handler for the Stylus-Down Event</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event-Args</param>
        void ctlComicViewer_StylusDown(object sender, StylusDownEventArgs e)
        {
            this._ManipulationProcessor.ProcessDown((uint)e.StylusDevice.Id, e.GetPosition(this).ToDrawingPointF());
        }

        // Touch Processing

        /// <summary>The Code to Process Multi-Touch Data</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event-Args</param>
        private void Process_TouchManipulationDelta(object sender, Windows7.Multitouch.Manipulation.ManipulationDeltaEventArgs e)
        {
            if (this._MultiTouchActive) // mutli-touch pan if active
            {
                this.trTranslate.X += e.TranslationDelta.Width;
                this.trTranslate.Y += e.TranslationDelta.Height;
            }
            if (this._MultiTouchRotate) // rotate if active
            {
                this.trRotate.Angle += e.RotationDelta * 180 / Math.PI;
            }
            if (this._MultiTouchZoom) // zoom if active
            {
                this.trScale.ScaleX *= e.ScaleDelta;
                this.trScale.ScaleY *= e.ScaleDelta;
            }
        }

        #endregion

        #region Page-Load

        /// <summary>internal Code to load a Comic-Page to the Viewer and display it</summary>
        /// <param name="PageData">the Page to Display</param>
        /// <param name="HasNext">TRUE if Next-Button should be enabled</param>
        /// <param name="HasPrevious">TRUE if Previous-Button should be enabled</param>
        private void _LoadPageData_(Comic.Common.cComicPage PageData, bool HasPrevious, bool HasNext)
        {
            // define if Banner will be shown
            if (PageData.PageBannerHtml == string.Empty) // no Banner-HTML - hide Browser
            {
                this._BannerAreaHeight = 0;
            }
            else // there is Banner-HTML - show Browser
            {
                this._BannerAreaHeight = 50;
            }

            // ToDo: save button-states (enable / disable buttons für next / prev.)

            // show Content
            this.imgComicPage.Source = this._ConvertToBitmapSource_(PageData.PageImage);


            // apply Settings
            this._ApplyBannerHeight_();
        }

        #endregion

        #region manual zooming

        /// <summary>internal code to zoom in</summary>
        private void _ZoomIn_()
        {
            this._ZoomCurrentImage_(1.1);
        }
        
        /// <summary>internal code to zoom out</summary>
        private void _ZoomOut_()
        {
            this._ZoomCurrentImage_(0.9);
        }

        /// <summary>Zoom the current Image (in our out) by Scale - takes care of re-centering image</summary>
        /// <param name="Scale">the scale to use (e.g. 1.1 or 0.9)</param>
        private void _ZoomCurrentImage_(double Scale)
        {
            // get values to work with
            double __tmpHeight = this.imgComicPage.ActualHeight; // get height in GUI (without Transform)
            double __tmpWidth = this.imgComicPage.ActualWidth; // get width in GUI (without Transform)
            double __tmpScaleX = this.trScale.ScaleX; // get current Scale for X
            double __tmpScaleY = this.trScale.ScaleY; // get current Scale for Y
            double __tmpNewScaleX = this.trScale.ScaleX * Scale; // get new Scale for X
            double __tmpNewScaleY = this.trScale.ScaleY * Scale; // get new Scale for Y
            // auto-center vertical
            if (Scale > 1) // scale greater then 1 (get bigger) - move lo left (-)
            {
                // (get new length of object by scale) and reduce by old size (before scale) --> take half of it as delta
                double __tmpMove = ((__tmpWidth * __tmpNewScaleX) - (__tmpWidth * __tmpScaleX)) / 2;
                this.trTranslate.X -= __tmpMove; // move by delta
            }
            if (Scale < 1) // scale lower then one (get samller) - move to right (+)
            {
                // if object gets too small, cancel zoom-out here
                if (((__tmpWidth * this.trScale.ScaleX) < this._MinImageSize) || ((__tmpHeight * this.trScale.ScaleY) < this._MinImageSize))
                    return;
                // (get new old size (before scale)) and reduce by new length (object with * new scale) --> take half of it as delta
                double __tmpMove = ((__tmpWidth * __tmpScaleX) - (__tmpWidth * __tmpNewScaleX)) / 2;
                this.trTranslate.X += __tmpMove; // move by delta
            }
            // if scale is exactly 1, nothing has changed and needs to be moved
            // finally apply new scale to the image
            this.trScale.ScaleX = __tmpNewScaleX;
            this.trScale.ScaleY = __tmpNewScaleY;

        }

        /// <summary>internal code to reset zoom</summary>
        private void _ZoomReset_()
        {
            this.trScale.ScaleX = 1;
            this.trScale.ScaleY = 1;
            this.trTranslate.X = 0;
            this.trTranslate.Y = 0;
        }

        #endregion

        #region manual scrolling and dragging

        private void _ScrollImageVertical_(bool ScrollUp)
        {
            if (ScrollUp) // scroll up
            {
                // ToDo: limit by element-size to be allways in screen
                this.trTranslate.Y += this._ScrollStepSize;
            }
            else // scroll down
            {
                // ToDo: limit by element-size to be allways in screen
                this.trTranslate.Y -= this._ScrollStepSize;
            }
        }

        private void _ScrollImageMyMouseDrag_(Point CurrentPosition)
        {
            // ToDo: start only, if delta is bigger then X - else it might be a click that must be handled diffrent (navigation forward / backward)
            // ToDo: limit move by screen-bounds
            //
            // calculate move delta
            double __moveX = (this._MouseDragLast.X - CurrentPosition.X);
            double __moveY = (this._MouseDragLast.Y - CurrentPosition.Y);
            // apply move-delta to transform-object
            this.trTranslate.X -= __moveX;
            this.trTranslate.Y -= __moveY;
            // remind this position for next calculation
            this._MouseDragLast = CurrentPosition;
            // debug out
            //this.lblDebug.Content = "move: " + __moveX.ToString() + " (x) - (y) " + __moveY.ToString();
        }

        #endregion

        #region SingleTouch Event Handler (Mouse)

        /// <summary>Handler for Single-Touch onMouseDown</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event-Args</param>
        private void area_MouseDown(object sender, MouseButtonEventArgs e)
        {
            // start tracking of move
            this._MouseDragStart = e.GetPosition(null);
            this._MouseDragLast = new Point(this._MouseDragStart.X, this._MouseDragStart.Y);
            this._MouseDragActive = this.imgComicPage.CaptureMouse();
        }

        /// <summary>Handler for Single-Touch onMouseMove</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event-Args</param>
        private void area_MouseMove(object sender, MouseEventArgs e)
        {
            if (this._MouseDragActive) // user is dragging
            {
                // perform image-move by mouse-move
                this._ScrollImageMyMouseDrag_(e.GetPosition(null));
            }
        }

        /// <summary>Handler for Single-Touch onMouseUp</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">not used</param>
        private void area_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (this._MouseDragActive) // user is dragging
            {
                // todo: add detection for "Click" --> verly small delta, short drag-time (less then 1 second)
                
                // end tracking of move
                this.imgComicPage.ReleaseMouseCapture();
                this._MouseDragActive = false;
            }
        }

        /// <summary>Handler for Single-Touch onMouseCaptureLost</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">not used</param>
        private void area_LostMouseCapture(object sender, MouseEventArgs e)
        {
            if (this._MouseDragActive) // user is dragging
            {
                // end tracking of move
                this.imgComicPage.ReleaseMouseCapture();
                this._MouseDragActive = false;
            }
        }

        /// <summary>Handler for Single-Touch Text-Input Drag-Interrupt</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">not used</param>
        private void area_TextInput(object sender, TextCompositionEventArgs e)
        {
            if (this._MouseDragActive) // user is dragging
            {
                // end tracking of move
                this.imgComicPage.ReleaseMouseCapture();
                this._MouseDragActive = false;
            }
        }

        /// <summary>Mouse-Wheel Event-Handler</summary>
        /// <param name="sender">not used</param>
        /// <param name="e">Event Args; used to detect direction</param>
        private void area_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            bool __doMove = false;
            bool __moveUp = false;
            if (e.Delta > 1) // moved forward (up)
            {
                __doMove = true;
                __moveUp = true;
            }
            if (e.Delta < 1) // moved backward (down)
            {
                __doMove = true;
            }
            // exit on no move
            if (!__doMove)
                return;
            // select type of action to trigger by mode
            if (this._UseWheelForZoom) // trigger zoom
            {
                if (__moveUp) // moved forward (up)
                    this._ZoomIn_();
                else // moved backward (down)
                    this._ZoomOut_();
            }
            else // trigger scroll
            {
                this._ScrollImageVertical_(__moveUp);
            }
        }

        #endregion

        private void _SingleTouchEvents_(bool BindEvents)
        {
            if (BindEvents)
            {
                this.imgComicPage.MouseLeftButtonDown += area_MouseDown;
                this.imgComicPage.MouseLeftButtonUp += area_MouseUp;
                this.imgComicPage.MouseMove += area_MouseMove;
                this.imgComicPage.TextInput += area_TextInput;
                this.imgComicPage.MouseWheel += area_MouseWheel;
                this.imgComicPage.LostMouseCapture += area_LostMouseCapture;
                this.imgComicPage.Cursor = System.Windows.Input.Cursors.ScrollAll;
            }
            else
            {
                this.imgComicPage.MouseLeftButtonDown -= area_MouseDown;
                this.imgComicPage.MouseLeftButtonUp -= area_MouseUp;
                this.imgComicPage.MouseMove -= area_MouseMove;
                this.imgComicPage.TextInput -= area_TextInput;
                this.imgComicPage.MouseWheel -= area_MouseWheel;
                this.imgComicPage.LostMouseCapture -= area_LostMouseCapture;
                this.imgComicPage.Cursor = System.Windows.Input.Cursors.Arrow;
            }
        }

        /// <summary>internal Code to apply the Multi-Touch-State for the Control</summary>
        private void _ApplyMultiTouchState_()
        {
            // if Deivice does not Support Multi-Touch, the n Override Setting with false
            if (!IsMultitouchEnabled)
                this._MultiTouchActive = false;
            // apply the internal state for multi-touch-elements
            if (!this._MultiTouchActive) // if turned off, set other MT-Settings to false, too...
            {
                this._MultiTouchRotate = false;
                this._MultiTouchZoom = false;
                // Event unRegister MultiTouch
                StylusDown -= ctlComicViewer_StylusDown;
                StylusUp -= ctlComicViewer_StylusUp;
                StylusMove -= ctlComicViewer_StylusMove;
                this._ManipulationProcessor.ManipulationDelta -= this.Process_TouchManipulationDelta;
                // Event Register Single-Touch
                this._SingleTouchEvents_(true);
            }
            else // turned on
            {
                // init MT-Factory (if required)
                if (!this._Factoryinitialized)
                {
                    Factory.EnableStylusEvents(this._ParentWindow);
                    this._Factoryinitialized = true;
                    // set PivotRadius once at this point
                    // (rotation angle for single finger manipulation)
                    this._ManipulationProcessor.PivotRadius = 2;    
                }
                // Event unRegister Single-Touch
                this._SingleTouchEvents_(false);
                // Event Register MultiTouch
                StylusDown += ctlComicViewer_StylusDown;
                StylusUp += ctlComicViewer_StylusUp;
                StylusMove += ctlComicViewer_StylusMove;
                this._ManipulationProcessor.ManipulationDelta += this.Process_TouchManipulationDelta;
            }
        }

        /// <summary>internal Code to apply the visibility of navigation-controls</summary>
        private void _ApplyControlVisibility_()
        {
            if (this._ShowNavigationControls) // should be visible
            {
                this.btnNextPage.Visibility = System.Windows.Visibility.Visible;
                this.btnPreviousPage.Visibility = System.Windows.Visibility.Visible;
            }
            else // should be hidden
            {
                this.btnNextPage.Visibility = System.Windows.Visibility.Hidden;
                this.btnPreviousPage.Visibility = System.Windows.Visibility.Hidden;
            }
        }

        /// <summary>internal Code to Apply the Banner-Height to the Control-Layout</summary>
        /// <remarks>used in PageLoad (for example)</remarks>
        private void _ApplyBannerHeight_()
        {
            // set height of Banner-Control
            this.webBannerHolder.Height = this._BannerAreaHeight;
            // set margin of image-control
            this.imgComicPage.Margin = new Thickness(0, 0, 0, this._BannerAreaHeight);
            // set margin of button-controls for navigation
            this.btnPreviousPage.Margin = new Thickness(10, 0, 0, this._BannerAreaHeight + 10);
            this.btnNextPage.Margin = new Thickness(0, 0, 10, this._BannerAreaHeight + 10);
        }

        /// <summary>Convert a System.Drawing.Image to a BitmapSource</summary>
        /// <param name="PageImage">The Image to Convert</param>
        /// <returns>the Input-Image as BitmapSource</returns>
        private BitmapSource _ConvertToBitmapSource_(System.Drawing.Image PageImage)
        {
            // create BitMap from Image
            System.Drawing.Bitmap __tmpBitmap = new System.Drawing.Bitmap(PageImage);
            // Convert to Image-Source and return result
            IntPtr hBitmap = __tmpBitmap.GetHbitmap();
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        }

    }
}